#ifndef DATA_IMAGE
#define DATA_IMAGE

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <assert.h>
#include <list>
#include <vector>
#include <string>
#include <sstream>

#include "BitmapLoader.h"

#define CLUSTER_UNCLASSIFIED -2
#define CLUSTER_NOISE -1

std::vector<std::string> &split(const std::string &s, char delim, std::vector<std::string> &elems) {
    std::stringstream ss(s);
    std::string item;
    while (std::getline(ss, item, delim)) {
        elems.push_back(item);
    }
    return elems;
}


std::vector<std::string> split(const std::string &s, char delim) {
    std::vector<std::string> elems;
    split(s, delim, elems);
    return elems;
}

using namespace std;

class ImageEntity {
    private:
        int clusterId;
        double density; // denclue
    public:
        unsigned long x;
        unsigned long y;

        ImageEntity(){
            clusterId = CLUSTER_UNCLASSIFIED;
            x = 0;
            y = 0;
        }

        void toString() const {
            printf("x:%lu y:%lu\n",
                   x, y
            );
        }

        double euclidicDistance(const ImageEntity *other) const {
            return sqrt(
                        (this->x - other->x) * (this->x - other->x)
                        + (this->y - other->y) * (this->y - other->y)
            );
        }

        double euclidicDistance(const ImageEntity &other) const {
            return 1;
            cout << "euclid " << this << &other << endl;
            return sqrt(
                        (this->x - other.x) * (this->x - other.x)
                        + (this->y - other.y) * (this->y - other.y)
            );
        }

        long double getEuclideanNorm() const {
            return sqrt(
                        (this->x) * (this->x)
                        + (this->y) * (this->y)
            );
        }

        void setClusterId(int id){
            clusterId = id;
        }

        int getClusterId() const {
            return clusterId;
        }

        void setDensity( double density ){
            this->density = density;
        }

        double getDensity() const {
            return this->density;
        }

        string getStringRepresentation( void ) const {
            ostringstream out_str;
            out_str << x << ',' << y;
            return out_str.str();
        };

        void entityFromString( const string& input ) {
            vector<std::string> x = split(input, ',');
            this->x = atol(x[0].c_str());
            this->y = atol(x[1].c_str());
            //cout << "entityFromString " << this->x << "," << this->y << endl;
        }

        unsigned int getComponentValue(int index) const {
            assert(index<=1);
            assert(index>=0);
            return index==0 ? x : y;
        }

        ostream& operator<<(ostream& os) const {
            os << '(';
            os << this->x << "," << this->y;
            os << ")" << " DENSITY ["<< this->getDensity() <<"]";
            return os;
        }

        unsigned int getNumOfDimensions() const {
            return 2;
        }

        ImageEntity operator*( long double scalar ) const {
            ImageEntity scaled(*this);
            scaled.x *= scalar;
            scaled.y *= scalar;
            return scaled;
        }

        ImageEntity operator+( const ImageEntity& operand ) const{
            ImageEntity sum;
            sum.x = this->x + operand.x;
            sum.y = this->y + operand.y;
            return sum;
        }
};

class ImageDatabase {
    unsigned long size;
    unsigned long maxx;
    unsigned long maxy;
    unsigned long minx;
    unsigned long miny;
    unsigned long dimension;

    public:
    ImageEntity db[2100000];

    void setSize(unsigned long size){
        this->size = size;
    }

    unsigned long getSize(){
        return size;
    }

	ImageDatabase(){
		dimension = 2;
	}

    ~ImageDatabase(){
        //
    }

    int load(char *filename, unsigned long recordLimit){
        ImageDatabase *pdb = this;
        unsigned long line=0;
        int scannedOnce=0;  // should be 9 as the number of entity properties
        int stopLoading=0;
        char devnull[5];
        ImageEntity lineEntity;
        FILE *fp;
        if ((fp=fopen(filename, "r"))==NULL) {
            //printf("File opened\tERROR\n");
            return 0;
        } else {
            //printf("File opened\tOK\n");
        }
        pdb->maxx = 0;
        pdb->maxy = 0;
        pdb->minx = 0;
        pdb->miny = 0;
        // skip first line
        fscanf(fp, "%*[^\n]\n");
        // load entities
        while(!stopLoading){
            scannedOnce=fscanf(fp, "%3c%lu%3c%lu",
               devnull, &lineEntity.x, devnull, &lineEntity.y
            );
            if (line%50000==0){
                //printf("scannedOnce %d, line %d\n", scannedOnce, line);
                printf(".");
            }
            //entityPrint(lineEntity);

            if (scannedOnce!=4){
                if (scannedOnce==2){
                    // date and time present, parameters not present
                    // skip one line
                    fscanf(fp, "%*[^\n]\n");
                    continue;
                } else {
                    stopLoading = 1;
                    continue;
                }
            }
            // correctly scanned
            memcpy( &pdb->db[line], &lineEntity, sizeof(lineEntity) );
            if (pdb->maxx < lineEntity.x){
                pdb->maxx = lineEntity.x;
            }
            if (pdb->maxy < lineEntity.y){
                pdb->maxy = lineEntity.y;
            }
            if (line%50000==0){
                //entityPrint(power->db[line]);
            }
            ++line;
            if (recordLimit!=0){
                if (line>=recordLimit){
                    stopLoading = 1;
                    continue;
                }
            }
        }
        fclose(fp);
        pdb->size = line+1;
        printf("\nFile loaded lines %lu, maxx:%lu, maxy:%lu\n", pdb->size, pdb->maxx, pdb->maxy);
        return 1;
    }

    int loadFromBMP(char *filename, int recordLimit){
        ImageDatabase *pdb = this;
        BMPFILE bmpo;
        BMPFILE *bmp = &bmpo;
        int i, j;
        int numEntities=0;
        int stopLoading=0;
        ImageEntity lineEntity;
        if ((loadBmp(&bmpo, filename))!=1) {
            return 0;
        } else {
        }

        pdb->maxx = bmp->info.biWidth-1;
        pdb->maxy = bmp->info.biHeight-1;
        pdb->minx = 0;
        pdb->miny = 0;

        if (bmp->info.biBitCount == 8){
            printf("BMP File must not be 8-bit");
        } else {
            for(j=0; j<bmp->info.biHeight; ++j){
                for(i=0; i<bmp->info.biWidth; ++i){
                    RGBQUAD* pix = &(bmp->pixels[j][i]);
                    if (pix->r + pix->g + pix->b > 0){
                        /// not black => no entity
                    } else {
                        /// black => entity
                        lineEntity.x = i;
                        lineEntity.y = j;
                        memcpy( &pdb->db[numEntities], &lineEntity, sizeof(lineEntity) );
                        if (numEntities%1000==0){
                            printf("x:%ld, y:%ld\n", pdb->db[numEntities].x, pdb->db[numEntities].y);
                        }
                        ++numEntities;
                        if (recordLimit!=0){
                            if (numEntities>=recordLimit){
                                stopLoading = 1;
                                break;
                            }
                        }
                    }
                }
                if (stopLoading){
                    break;
                }
            }
        }
        /// scanned correctly
        destroyBmp(&bmpo);
        pdb->size = numEntities;
        printf("\nFile loaded entities %d\n", numEntities);
        return 1;
    }

    int saveToBMP(char *filename){
        return saveToBMP(filename, 1);
    }

    int saveToBMP(char *filename, unsigned int divBy){
        unsigned long i,j;
        RGBQUAD white;
        BMPFILE bmpo;
        BMPFILE *bmp = &bmpo;

        white.r = 255;
        white.g = 255;
        white.b = 255;

        bmp->header.bfType =0x4d42;
        bmp->header.bfSize = (maxx/divBy+1) * (maxy/divBy+1) * 3 + 54;
        bmp->header.bfReserved1 = 0;
        bmp->header.bfReserved2 = 0;
        bmp->header.bfOffBits = 54;

        bmp->info.biSize    = 40;
        bmp->info.biWidth   = (maxx/divBy+1);
        bmp->info.biHeight  = (maxy/divBy+1);
        bmp->info.biPlanes  = 1;
        bmp->info.biBitCount    = 24;
        bmp->info.biCompression = 0;
        bmp->info.biSizeImage   = (maxx/divBy) * (maxy/divBy) * 3;
        bmp->info.biXPelsPerMeter   = 0;
        bmp->info.biYPelsPerMeter   = 0;
        bmp->info.biClrUsed         = 0;
        bmp->info.biClrImportant    = 0;

        bmp->pixels = (RGBQUAD**)malloc(bmp->info.biHeight*sizeof(RGBQUAD*));
        for (i=0; (int)i<bmp->info.biHeight; ++i){
            bmp->pixels[i] = (RGBQUAD*)malloc(bmp->info.biWidth*sizeof(RGBQUAD));
        }

        for(j = 0; (int)j < bmp->info.biHeight; j++){
            for(i = 0; (int)i < bmp->info.biWidth; i++){
                bmp->pixels[j][i] = white;
            }
        }
        //printf("wx:%lu wy:%lu\n", bmp->info.biWidth, bmp->info.biHeight);
        for (i=0; (unsigned)i<size; ++i){
            ImageEntity *ie = &db[i];
            //printf("x:%lu y:%lu\n", ie->x/divBy, ie->y/divBy);
            /*if (ie->y > 65000){
                printf("too big y\n");
                break;
            }
            if (ie->x > 650000){
                printf("too big x\n");
                break;
            }*/
            bmp->pixels[ie->y/divBy][ie->x/divBy] = generateColorFromInt(ie->getClusterId());
            //bmp->pixels[0][0] = generateColorFromInt(ie->getClusterId());
        }

        return saveBmp(bmp, filename);
    }

    double maxDistance(){
        ImageDatabase *power = this;
        unsigned int i;
        double r, max;
        max = 0.0;
        for(i=0; i<power->size; ++i){
            r = power->db[0].euclidicDistance(&power->db[i]);
            //printf("Dist %d %d = %lf\n", 0, i, r);
            if (r>max){
                max = r;
            }
        }
        return max;
    }

    /// DATASET INDEPENDENT BELOW

    list<ImageEntity*>* getEpsilonNeighborhood(ImageEntity * source, double epsilon){
        ImageDatabase *power = this;
        list<ImageEntity*>* neighborhood = new list<ImageEntity*>();
        unsigned int i;
        double r;
        assert(source);
        for(i=0; i<power->size; ++i){
            r = source->euclidicDistance(&power->db[i]);
            //printf("%lf\t",r);
            //printf("Dist %d %d = %lf\n", 0, i, r);
            if (r<=epsilon){
                if (&power->db[i]!=source){
                    neighborhood->push_back(&power->db[i]);
                }
            }
        }
        return neighborhood;
    }

    // Denclue algorithm

    const vector<unsigned int> retrieveUpperBound() const
	{
		vector<unsigned int> *upper_bounds = new vector<unsigned int>();
		upper_bounds->push_back(this->maxx);
		upper_bounds->push_back(this->maxy);
		return (*upper_bounds);
	}

	const vector<unsigned int> retrieveLowerBound() const
	{
		vector<unsigned int> *lower_bounds = new vector<unsigned int>();
		lower_bounds->push_back(this->minx);
		lower_bounds->push_back(this->miny);
		return (*lower_bounds);
	}

};

#endif
